/*
    服务端
    简单版的断开重连 加 群聊
    IO 多路复用 select
*/

#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/time.h>
#include <sys/select.h>

typedef struct 
{
    int alive;  // 标记是否存活 0表示存活 1表示退出
    int net_fd; //与某个客户端连接的socket对象的文件描述符
}conn_t;

int main()
{
    char *port = "8888";
    char *ip = "0.0.0.0";
    // 一样先创建socket对象
    int sever_fd = socket(AF_INET, SOCK_STREAM, 0);
    if(sever_fd == -1)
    {
        perror("创建socket对象失败！");
        exit(1);
    }
    // 如果某个端口处于time_wait状态，我们可以通过如下的这个setsockopt函数设置socket重新使用time_wait端口
    int reuse = 1;
    setsockopt(sever_fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));
    // 设置端口，开始监听
    struct sockaddr_in addr;
    addr.sin_addr.s_addr = inet_addr(ip);
    addr.sin_family = AF_INET;
    addr.sin_port = htons(atoi(port));
    // 参数 绑定的那个socket对象，绑定的端口的端口 结构体大小
    int ret = bind(sever_fd, (struct sockaddr*)&addr, sizeof(addr));
    if(ret == -1)
        perror("bind");
    // 开始监听 ,并设置队列的大小
    listen(sever_fd, 10);

    // 定义一个数组
    conn_t list[100];
    memset(list, 0, sizeof(list));
    int size = 0;   //标记数组中socket对象的个数

    // 用来保存下一次循环监听谁
    fd_set base_set;
    FD_ZERO(&base_set);
    FD_SET(sever_fd, &base_set);

    int new_fd;
    while(1)
    {
        fd_set set;
        FD_ZERO(&set);
        // 将下一次要监听的文件描述符集合 复制给set
        memcpy(&set, &base_set, sizeof(base_set));
        
        select(101, &set, NULL, NULL, NULL);
        if(FD_ISSET(sever_fd, &set))
        {
            // 有新的连接来了
            new_fd = accept(sever_fd, NULL, NULL);
            // 存储到数组中
            list[size].net_fd = new_fd;
            list[size].alive = 0;
            size++;
            // 放入监听集合中
            FD_SET(new_fd, &base_set);
        }
        // 判断是否有客户端发消息了，确定是哪一个客户端
        // 遍历所有客户端，判断是否在就绪集合中，如果在给其他的客户端发消息
        for(int i = 0; i < size; i++)
        {
            if(list[i].alive == 0 && FD_ISSET(list[i].net_fd, &set))
            {
                // 在监听集合中
                // 说明list[i].net_fd 所代表的客户端是正常连接
                // 并且，这个连接在就绪集合，有发信息过来
                char buf[60] = {0};
                int recv_ret = recv(list[i].net_fd, buf, sizeof(buf), 0);
                if(recv_ret == 0)
                {
                    // 读到数据的个数为0 并且可读 说明对方断开连接
                    // 将链接的状态修改
                    // 移除监听
                    FD_CLR(list[i].net_fd, &base_set);
                    list[i].alive = 1;
                    // 关闭连接
                    close(list[i].net_fd);
                    continue;
                }
                // 走到这说明没有断开连接，并且读到了数据，进行转发
                for(int j = 0; j < size; j++)
                {
                    if(list[j].alive == 0 && list[j].net_fd != list[i].net_fd)
                    {
                        // 转发
                        send(list[j].net_fd, buf, sizeof(buf), 0);
                    }
                }
            }
        }
    }
    close(sever_fd);
    return 0;
}